﻿using OnlineTestsRest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http;
using OnlineTestsModels;
using OnlineTestsDataLayer;
using OnlineTestsRest.Models.UserModels;
using System.Web.Http.ValueProviders;
using Forum.WebAPI.Attributes;
using System.Web.Http.Cors;

namespace OnlineTestsRest.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class UsersController : ApiController
    {
        private const int MinUsernameLength = 6;
        private const int MaxUsernameLength = 30;
        private const int MinPasswordLength = 6;
        private const int MaxPasswordLength = 30;
        private const int SessionKeyLength = 50;

        private const string ValidUsernameCharacters =
            "qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM1234567890_.";
        private const string SessionKeyChars =
            "qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM";

        private static Random randomGen = new Random();

        [HttpPost]
        [Route("api/users/login/")]
        public HttpResponseMessage PostLogin([FromBody]UserLoginModel model)
       {
            try
            {
                this.ValidateUser(model);
            }
            catch (ArgumentException ae)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, ae.Message);
            }

            string loweredUsername = model.Username.ToLower();

            TestsContext db = new TestsContext();

            User userEntity = db.Users.FirstOrDefault(
                u => u.Username == loweredUsername && u.Password == model.Password);

            if (userEntity == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
                    "Invalid username or password");
            }

            if (userEntity.SessionKey == null)
            {
                userEntity.SessionKey = this.GenerateSessionKey(userEntity.Username);
                db.SaveChanges();
            }

            UserLoggedModel loggedModel = new UserLoggedModel()
            {
                Username = userEntity.Username,
                SessionKey = userEntity.SessionKey,
            };

            return this.Request.CreateResponse(HttpStatusCode.OK, loggedModel);
        }

        [HttpPost]
        [Route("api/users/logout/")]
        public HttpResponseMessage PostLogout(
              [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            TestsContext db = new TestsContext();

            User entity = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (entity != null)
            {
                entity.SessionKey = null;
                db.SaveChanges();
            }

            return this.Request.CreateResponse(HttpStatusCode.OK, "Session key removed");
        }

        private string GenerateSessionKey(string username)
        {
            StringBuilder sessionKeySb = new StringBuilder(SessionKeyLength);
            sessionKeySb.Append(username);

            while (sessionKeySb.Length < SessionKeyLength)
            {
                sessionKeySb.Append(SessionKeyChars[randomGen.Next(SessionKeyChars.Length)]);
            }

            return sessionKeySb.ToString();
        }

        private void ValidateUser(UserLoginModel user)
        {
            if (user == null)
            {
                throw new ArgumentNullException("Empty user");
            }

            ValidateUsername(user.Username);
            ValidatePassword(user.Password);
        }

        private void ValidatePassword(string password)
        {
            if (string.IsNullOrWhiteSpace(password) || password.Length < MinPasswordLength
                || password.Length > MaxPasswordLength)
            {
                throw new ArgumentException(string.Format(
                    "User must have password with lenght between {0} and {1} characters", MinPasswordLength, MaxPasswordLength));
            }
        }

        private void ValidateUsername(string username)
        {
            if (string.IsNullOrWhiteSpace(username) || username.Length < MinUsernameLength
               || username.Length > MaxUsernameLength)
            {
                throw new ArgumentException(string.Format(
                    "User must have username with length between {0} and {1} characters.", MinUsernameLength, MaxUsernameLength));
            }

            if (username.Any(ch => !ValidUsernameCharacters.Contains(ch)))
            {
                throw new ArgumentException("Username can contain only latin letters, digit, underscore and dot");
            }
        }
    }
}
